/**
 * \file ping_tcp.c
 * \brief ping en TCP
 * \author Pierre Neumann / Julien Meutelet
 * \version 0.1
 * \date 20 decembre 2013
 *
 * Gère l'envoi de ping en TCP
 *
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stddef.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>

#include "ping_tcp.h"
#include "packet_ip.h"
#include "packet_icmp.h"
#include "packet_tcp.h"
#include "packet_utils.h"

/**
 * \fn ping_tcp(char* ip,int port,int ttl,int timeout)
 * \brief Initialise un paquet TCP
 *
 * \param ip Chaine de caractère représentant l'adresser IP à pinger.
 * \param port Entier pour le port à utiliser.
 * \param ttl Entier utilisé pour initialiser le champ ttl de l'entête IP.
 * \param timeout Entier, timeout avant de considérer un paquet perdu.
 * \return Structure ping_result.
 */
struct ping_result* ping_tcp(char* ip,int port,int ttl,int timeout){ 
	// On déclare les différents timers.
	struct timeval before, after, tv;
	tv.tv_sec = timeout;
  	tv.tv_usec = 0;
	// On alloue le resultat 
	struct ping_result* res = malloc(sizeof(struct ping_result));
	// On déclare quelques variables utiles
        int sockfd, sockfdr, optval; 

	socklen_t addrlen = sizeof(struct  sockaddr);

	struct sockaddr_in server ;
	server.sin_family = AF_INET; 
        server.sin_addr.s_addr = inet_addr(ip);
	
	// On intialise les deux socket de retour 
	// On veut recevoir du tcp et icmp
        sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
	sockfdr = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
 
	if(sockfd == -1){
		 perror("Erreur creation socket");
      		 exit(1);
    	} 
	if(sockfdr == -1){
		 close(sockfd);
		 perror("Erreur creation socket");
      		 exit(1);
    	}    
	  
	// Option pour que la soket ne crée pas sa propre entête IP  
        if(setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&optval,sizeof(int)) < 0){
		close(sockfd);
		close(sockfdr);
		perror("Erreur setsockopt");
      		exit(1);
	}   	

	// Paquet à envoyer
	char *packet_ping = NULL;
	// On récupère l'adresse IP de la machine
	struct in_addr my_ip = get_my_ip();
	// On initialise le paquet
	init_tcp_header(&packet_ping,&(server.sin_addr),&my_ip,port,ttl);
	  
	// Paquet reponse
	char *packet_pong = (char *) malloc(TCP_PACKET_SIZE);
	if(packet_pong == NULL || packet_ping == NULL){
		exit(1);
	}

	// On récupère le temps avant l'envoi
	gettimeofday (&before, NULL);

	// Envoi du paquet
        if(sendto(sockfd,packet_ping,TCP_PACKET_SIZE,0,(struct sockaddr*)&server,addrlen) <=0){
		perror("Erreur sendto()");
	}
            
	// On fait un select pour écouter sur les deux socket

	fd_set set;
        FD_SET(sockfd, &set);
	FD_SET(sockfdr, &set);
	int max_fd;
	if(sockfd > sockfdr) max_fd = sockfd;
	else max_fd = sockfdr;
	// Select avec Timeout 
    	int returned = select(max_fd+1, &set, NULL, NULL, &tv);
	int pong_size = 0;
	if (returned>0) {
		if (FD_ISSET(sockfd, &set)) {
			pong_size = recvfrom(sockfd,packet_pong,TCP_PACKET_SIZE,0,NULL,NULL);
		}
		if (FD_ISSET(sockfdr, &set)) {
			pong_size = recvfrom(sockfdr,packet_pong,ICMP_PACKET_SIZE,0,NULL,NULL);
		}
	}
	// Si Timeout dépassé ou erreur 
	else{
		close(sockfd);
		close(sockfdr);
        	free(packet_ping);
        	free(packet_pong);
		free(res);
		return NULL;
	}
	
	// On récupère le temps après reception
	gettimeofday (&after, NULL);

	// On initialise la structure de retour
	init_ping_result(res,&packet_pong,pong_size,((after.tv_sec-before.tv_sec)*1000000L+after.tv_usec)-before.tv_usec); 
 
	// On libère la mémoire
        close(sockfd);
	close(sockfdr);
        free(packet_ping);
        free(packet_pong);
        return res;
}
